<!--
 * @Description: 表格工具栏
 * @Author: HuiSir<273250950@qq.com>
 * @Date: 2021-10-27 18:09:14
 * @LastEditTime: 2025-09-15 12:11:27
-->
<template>
  <div>
    <div
      v-if="hasSearchTool"
      v-show="showSearchForm"
      v-loading="searchFormLoading"
      class="search-from-box"
    >
      <fy-form
        ref="formRef"
        v-model="searchFormParams"
        :options="searchFormOptions"
        :labelWidth="searchFormLabelWidth"
        @submit="$emit('searchSubmit')"
        @reset="$emit('searchReset')"
      >
        <!-- 搜索表单插槽 -->
        <template
          v-for="item in searchSlotOpts"
          :key="item.key"
          v-slot:[item.slot]
        >
          <slot :name="item.slot" :val="searchFormParams" :tableRef="elTable" />
        </template>
      </fy-form>
    </div>
    <div v-if="showTools" class="table-tools">
      <div class="left">
        <slot
          name="left"
          :tableRef="elTable"
          :search="{ data: searchFormParams, options: searchFormOptions }"
        />
        <el-button
          v-for="btn in leftBtns"
          :key="btn.name"
          :type="btn.type"
          :disabled="btn.disabled"
          :loading="btn.name === 'delete' && deleteLoading"
          @click="handleBtnClick(btn)"
        >
          <fy-icon
            v-if="
              (btn.name === 'delete' && !deleteLoading) || btn.name !== 'delete'
            "
            :name="btn.icon"
            size="13"
            color="white"
          />
          {{ btn.title }}
        </el-button>
      </div>
      <div class="right">
        <slot
          name="right"
          :tableRef="elTable"
          :search="{ data: searchFormParams, options: searchFormOptions }"
        />
        <template v-for="btn in rightBtns" :key="btn.name">
          <el-tooltip
            v-if="btn.dropdown"
            effect="light"
            :content="btn.title"
            placement="top"
            :auto-close="1000"
          >
            <el-dropdown
              :class="{ disabled: btn.disabled }"
              popper-class="right-drop-menu"
              :hide-on-click="btn.name === 'export'"
              trigger="click"
              max-height="300px"
              @command="handleDropdown"
              @visibleChange="$emit('btnClick', btn.name, $event)"
            >
              <div
                class="tool-item el-dropdown-link"
                :class="{ [btn.name]: true }"
              >
                <fy-icon
                  :name="btn.icon"
                  color="var(--el-text-color-regular)"
                  size="16"
                />
              </div>
              <template #dropdown>
                <el-dropdown-menu>
                  <template v-if="btn.name === 'cols'">
                    <el-checkbox-group v-model="showCols">
                      <el-dropdown-item v-for="label in colLabels" :key="label">
                        <el-checkbox :label="label" :value="label" />
                      </el-dropdown-item>
                    </el-checkbox-group>
                  </template>
                  <template v-if="btn.name === 'export'">
                    <el-dropdown-item :command="1">导出csv </el-dropdown-item>
                    <el-dropdown-item :command="2">导出excel </el-dropdown-item>
                  </template>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </el-tooltip>
          <el-tooltip
            v-else
            effect="light"
            :content="btn.title"
            placement="top"
            :auto-close="1000"
          >
            <div
              class="tool-item"
              :class="{ [btn.name]: true, disabled: btn.disabled }"
              @click="handleBtnClick(btn)"
            >
              <fy-icon
                :name="btn.icon"
                color="var(--el-text-color-regular)"
                size="16"
              />
            </div>
          </el-tooltip>
        </template>
      </div>
    </div>
    <!-- 新增弹窗 -->
    <fy-form-dialog
      v-if="hasAddTool"
      v-model="showAddDialog"
      v-model:options="addFormOptions"
      v-bind="addDialogProps"
      :loading="dialogLoading"
      :params="currAddData"
      @submit="handleAddSubmit"
      @upload:change="bindUploadChange"
    >
      <!-- 编辑弹窗表单插槽 -->
      <!-- @ts-expect-error 临时忽略错误 -->
      <template
        v-for="item in addDialogSlotOptions"
        :key="item.key"
        v-slot:[item.slot]="scope"
      >
        <slot :name="item.slot" v-bind="scope" />
      </template>
    </fy-form-dialog>
  </div>
</template>

<script lang="ts">
export default {
  name: "TableTools",
};
</script>

<script lang="ts" setup>
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ref, PropType, Ref, computed, useSlots } from "vue";
import useTableTools from "./useTableTools";
import {
  ICol,
  ITableTool,
  TOptionOfTools,
  FyTableInstance,
  IFormOption,
  SubmitCallbacks,
  DeleteCallbacks,
  Awaitable,
  UploadEventParams,
  UploadChangeForm,
  IFormDialogProps,
} from "../types";
import useTableToolsAction from "./useTableToolsAction";
import { ElMessage, ElMessageBox } from "element-plus";
import { deepCopy } from "../../helpers";

const props = defineProps({
  elTable: {
    type: Object as PropType<Ref<FyTableInstance | null>>,
    required: true,
  },
  tools: {
    type: Array as PropType<TOptionOfTools[]>,
    default: () => [],
  },
  /**
   * 搜索表单初始状态，不设为默认隐藏
   */
  showSearchFormInitial: {
    type: Boolean,
    default: false,
  },
  /**
   * 新增弹窗固定参数
   */
  addDialogProps: {
    type: Object as PropType<IFormDialogProps>,
    default: () => ({}),
  },
  /**
   * 搜索表单标签宽度
   */
  searchFormLabelWidth: {
    type: [String, Number],
    default: 80,
  },
  /**
   * 表单项加载钩子
   * 这里可以对表单项数据进行处理或执行请求
   */
  beforeSearchFormLoad: {
    type: Function as PropType<
      (
        formData: Ref<Record<string, unknown>>,
        formOptions: Ref<IFormOption[]>
      ) => Awaitable<void>
    >,
  },
  /**
   * 新增弹窗默认数据
   */
  addFormDefaultData: {
    type: Object as PropType<Record<string, unknown>>,
    default: () => ({}),
  },
  /**
   * 新增弹窗弹出前钩子，用于处理表单默认展示的数据
   */
  beforeShowAddDialog: {
    type: Function as PropType<
      (
        formData: Ref<Record<string, unknown>>,
        addFormOptions: Ref<IFormOption[]>
      ) => Awaitable<void>
    >,
  },
  /**
   * 文件上传change事件
   */
  uploadChange: {
    type: Function as PropType<
      (uploadParams: UploadEventParams, form: UploadChangeForm) => void
    >,
  },
});

/**
 * 表格列，双向绑定用于调整列显隐
 */
const cols = defineModel("cols", {
  type: Array as PropType<ICol[]>,
  required: true,
});

/**
 * 新增弹窗表单配置项
 */
const addFormOptions = defineModel("addFormOptions", {
  type: Array as PropType<IFormOption[]>,
  default: () => [],
});

/**
 * 搜索表单配置项
 */
const searchFormOptions = defineModel("searchFormOptions", {
  type: Array as PropType<IFormOption[]>,
  default: () => [],
});

/**
 * 搜索表单参数
 */
const searchFormParams = defineModel("searchFormParams", {
  type: Object as PropType<Record<string, any>>,
  default: () => ({}),
});

const emit = defineEmits<{
  searchSubmit: [];
  searchReset: [];
  // 元组语法：明确参数名称和类型
  dialogSubmit: [
    params: Record<string, unknown>,
    callbacks: SubmitCallbacks // 对象参数
  ];

  btnClick: [btn: TOptionOfTools, args?: any];
  delete: [selection: FyTableInstance["selection"], callbacks: DeleteCallbacks];
  refresh: [];
}>();

// 表单组件
const formRef = ref();

// 所有按钮
const { left, right } = useTableTools();

// 是否有搜索表单
const hasSearchTool =
  props.tools.includes("search") || props.showSearchFormInitial;

// 是否有工具栏
const slots = useSlots();
const showTools = computed(
  () => props.tools.length > 0 || slots.left || slots.right
);

// 是否有新增按钮
const hasAddTool = props.tools.includes("add");

// 左侧按钮组
const leftBtns = left.filter((item: ITableTool) =>
  props.tools.includes(item.name)
);

// 右侧按钮组
const rightBtns = right.filter((item: ITableTool) =>
  props.tools.includes(item.name)
);

// 搜索表单栏状态
const showSearchForm = ref(props.showSearchFormInitial);

// 表格折叠状态
const treeTableExpanded = ref(false);

// 部分工具栏固定方法
const { selectShowCols, toggleTreeTableAll, exportCurrPage } =
  useTableToolsAction(props.elTable, cols);
const { colLabels, showCols } = selectShowCols();

// 导出
const handleDropdown = (e: number) => {
  if (typeof e === "number") {
    exportCurrPage(e);
  }
};

// 搜索表单插槽配置项
const searchSlotOpts = computed(() =>
  searchFormOptions.value.filter((opt: { slot?: string }) => opt.slot)
);

// 新增弹窗表单插槽
const addDialogSlotOptions = computed(() =>
  addFormOptions.value.filter((opt: { slot?: string }) => opt.slot)
);

// 搜索表单加载钩子
const searchFormLoading = ref(false);
(async () => {
  if (props.beforeSearchFormLoad) {
    searchFormLoading.value = true;
    await props.beforeSearchFormLoad(searchFormParams, searchFormOptions);
    searchFormLoading.value = false;
  }
})();

const showAddDialog = ref(false);
const dialogLoading = ref(false);
const currAddData = ref({});
const deleteLoading = ref(false);

// 编辑完成确认
const handleAddSubmit = (
  formData: Record<string, unknown>,
  callbacks: SubmitCallbacks
) => emit("dialogSubmit", formData, callbacks);

// 按钮点击
const handleBtnClick = async (btn: ITableTool) => {
  if (!btn.disabled) {
    if (["delete"].includes(btn.name)) {
      // 删除
      const selection = props.elTable.value!.selection;
      if (!(selection && selection.length)) {
        ElMessage.warning("请勾选一行！");
      } else {
        ElMessageBox.confirm("确定要删除吗？删除后不可恢复！", {
          title: "提示",
        })
          .then(() => {
            deleteLoading.value = true;

            emit("delete", selection, {
              closeLoading() {
                deleteLoading.value = false;
              },
            });

            // 这里给出延时关闭loading,避免忘记执行closeLoading方法导致loading永久
            // 一般请求执行1.5秒也该关了
            const timer = setTimeout(() => {
              deleteLoading.value = false;
              clearTimeout(timer);
            }, 1500);

            emit("btnClick", btn.name, selection);
          })
          .catch(() => {
            // catch error
          });
      }
    } else if (btn.name === "search") {
      // 搜索按钮：显隐表单
      showSearchForm.value = !showSearchForm.value;
      if (!showSearchForm.value) {
        // 隐藏表单搜索栏时重置，既然有搜索表单显隐按钮，那么formRef一定存在
        formRef.value.reset();
      }
      emit("btnClick", btn.name, showSearchForm.value);
    } else if (btn.name === "fold") {
      // 展开折叠
      treeTableExpanded.value = !treeTableExpanded.value;
      toggleTreeTableAll(treeTableExpanded.value);
      emit("btnClick", btn.name, treeTableExpanded.value);
    } else if (btn.name === "refresh") {
      // 刷新,这里不清空搜索表单
      emit("refresh");
      emit("btnClick", btn.name);
    } else if (btn.name === "add") {
      // 拷贝数据
      const copyFormData = ref(deepCopy(props.addFormDefaultData));
      // 先弹出弹窗（避免加载数据时不好的用户体验）
      showAddDialog.value = true;
      // 给出loading
      dialogLoading.value = true;
      // 新增前钩子
      if (props.beforeShowAddDialog) {
        await props.beforeShowAddDialog(copyFormData, addFormOptions);
      }
      // 经过处理的行数据赋值给最终展示的默认数据
      currAddData.value = copyFormData.value;
      // 关闭loading
      dialogLoading.value = false;
      // 通知父组件
      emit("btnClick", btn.name);
    } else {
      // 通知父组件
      emit("btnClick", btn.name);
    }
  }
};

/**
 * 文件更新事件
 */
const bindUploadChange = (
  uploadParams: UploadEventParams,
  form: UploadChangeForm
) => {
  if (props.uploadChange) {
    props.uploadChange(uploadParams, form);
  }
};
</script>

<style scoped lang="scss">
.table-tools {
  display: flex;
  justify-content: space-between;
  margin-bottom: 15px;

  .left {
    :deep(.fy-icon) {
      margin-right: 4px;
      margin-left: -2px;
    }
  }

  .left,
  .right {
    display: flex;
  }

  .right {
    .tool-item {
      width: 30px;
      height: 30px;
      display: flex;
      justify-content: center;
      align-items: center;
      border: var(--el-border);
      cursor: pointer;
      transition: 0.3s all;
      outline: none !important;
      margin-left: 10px;

      &:not(.disabled):active {
        background: var(--el-background-color-base);
        --el-text-color-regular: var(--el-color-primary);
        outline: none;
        border: none;
      }
    }

    .disabled {
      opacity: 0.5;
      pointer-events: none;
    }
  }
}
</style>
<style lang="scss">
.right-drop-menu ul.el-dropdown-menu {
  .el-checkbox__label {
    color: unset !important;
  }

  .el-checkbox {
    height: 22px;
  }
}
</style>
